﻿/* 
 **************************************************************************
	 _____   _____   _   _   
	| ____| |  _  \ | | / /  
	| |__   | | | | | |/ /   
	|  __|  | | | | | |\ \   
	| |___  | |_| | | | \ \  
	|_____| |_____/ |_|  \_\ 
 
	Version: 	0.1.2
	Author: 	Frank Cheung
	Email: 		frank@ajaxjs.com
	Web: 		www.ajaxjs.com
	
	本代码可以免费使用、修改，希望我的程序能为您的工作带来方便，同时请保留以上请息。

	---Edk Licenses: Berkeley Software Distribution，BSD.-----

	BEGIN LICENSE BLOCK
	
	Copyright 版权所有 (c) 2011 Frank Cheung
	任何获得本软件副本及相关文档文件（下面简称为“软件”）的个人都可以免费获得不受限制处置本软件的权限，
	包括不受限制地使用、复制、修改、合并、出版、分发、重新许可或者销售本软件的副本，
	并且在满足下述条件时，允许本软件的受让人获得下述权限：

	在本软件的所有或者重要部分中包含上述版权公告信息和本权限公告信息。

	本软件不提供保证，不包含任何类型的保证（无论是明指的还是暗喻的），
	包含但不限于关于本软件的适销性、特定用途的适用性和无侵权保证。
	在任何情况下，无论是否签订了合约、存在侵权行为还是在其他情况下，
	本软件作者或版权持有人不对由本软件直接或间接产生的
	或由使用本软件或处置本软件所产生的任何索赔、损坏或者其他责任。
	
	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:
	
	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.
	
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
	
	END LICENSE BLOCK

 ***************************************************************************/

//*************************************************************************
// Edk.js它具备very core的特性。
// 此外，它既可以在客户端运行，也可以满足服务端DSL的要求，一物两用。
//*************************************************************************
/* 
 * 初始化命名空间。$$表示Edk，何解？没什么，只是因为Edk不如敲打“$$”来得轻松：） 
if(typeof exports !== "undefined") {
	$$ = exports;
} else {
	$$ = {};// if(!this['$$'])  $$ = {};
}
*/
var $$ = $$ || this.exports || {};

/**
 * Douglas Crockford 基于原型的基础方法。
 * Ref. http://javascript.crockford.com/prototypal.html
 * newObject = Object.create(oldObject);
 * 2008-04-07
 * @param {Object} ource 原型对象
 */
if(!Object.create){
	Object.create = function(source){
		var dummy;
		dummy = source.constructor != Object ? source.constructor : function (){};
		dummy.prototype	= source;
		
		return new dummy(arguments[1], arguments[2], arguments[3], arguments[4]);
	}
}

/**
 * 拷贝对象。
 * @param {Object} a 目标对象
 * @param {Object} b 源对象
 */
$$.apply = function(a, b){
	a = a || new Object;
	for(var i in b)
		a[i] = b[i];
	return a;
}

/**
 * 检查对象的类型。支持 null/undefined/String/Array/Nubmer/Boolean/Date/RegExp。
 * @param	{Any}		foo 要被检查类型的对象。
 * @return	{Funciton}
 */
$$._typeof = function(foo){
	if (foo === null) {
		return 'null';
	}else if(typeof foo == 'undefined'){
		return 'undefined';
	}
	
	if(foo instanceof Boolean){
		return Number;
	}else if(foo instanceof Number){
		return Number;
	}else if(foo instanceof String){
		return String;
	}else if(foo instanceof Array){
		return Array;
	}else if(foo instanceof Date){
		return Date;
	}else if(foo instanceof RegExp){
		return;
	}
	
	switch (foo.constructor){
		case Boolean :
			return Number;
			break;
		case Number :
			return Number;
			break;
		case String :
			return String;
			break;
		case Array :
			return Array;
			break;
		case Date :
			return Date;
			break;
		case RegExp :
			return;
			break;
	}
	
	switch (typeof foo){
		case 'undefined' :
		case 'unknown' :
		case 'function' :
		case 'regexp' :
			return;
			break;
		case 'boolean' :
		case 'number' :
			return Number;
			break;
		case 'date' :
			return Date;
			break;
		case 'string' :
			return String;
		case 'object' :
			return Object;
			break;
		case 'array' :
			return Array;
	}
}

/**
 * 将一个数组插入另外一个数组的指定位置
 * @param	{Array}		target
 * @param	{Number}	insertIndex 
 * @param	{Array}		arr
 * @return	{Array}
 */
Array.insert = function(target, insertIndex, arr){
	return target.splice.apply(target, Array.concat.call(insertIndex, 0, arr));
}

Array.indexOf = function(arr, el){
	for(var i = 0, j = arr.length; i < j; i++){
		if(arr[i] == el){
			return i;
		}
	}
	return -1;
}

Array.remove = function(arr, el){
	var index = Array.indexOf(arr, el);
	arr.splice(index, 1);
	return el;
}

/**
 * 定义带标记的字符串，并用传入的字符替换标记。每个标记必须是唯一的，而且必须要像{0},{1}...{n}这样地自增长。
 * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens.  Each
 * token must be unique, and must increment in the format {0}, {1}, etc.  
 * 例如：Example usage:
 * <pre><code>
var cls = 'my-class', text = 'Some text';
var s = String.format('&lt;div class="{0}">{1}&lt;/div>', cls, text);
//s现在是字符串：s now contains the string: '&lt;div class="my-class">Some text&lt;/div>'
</code></pre>
 * @param  {String} string 带标记的字符串，或者可变长度的字符串参数。The tokenized string to be formatted, or arguments list
 * @param  {String} value1 第一个值，替换{0}。The value to replace token {0}
 * @param  {String} value2 第二个值，替换{1}...等等（可以有任意多个）。Etc...
 * @return {String} 转化过的字符串。The formatted string
 */
String.prototype.format = (function(){
	var 
	 regexp 	= /{(\d+)}/g
	,replace 	= String.prototype.replace;
	
	function replaceEl(m, i){
		return this[i]; // this为arguments
	};

	return function(){
		replaceEl.scope = arguments;// 委托作用域
		return replace.call(this, regexp, replaceEl.delegate());
	}
})();

/**
 * 裁剪字符串两旁的空白符，保留中间空白符，例如：
 * Trims whitespace from either end of a string, leaving spaces within the string intact.  Example:
 * <pre><code>
var s = '  foo bar  ';
alert('-' + s + '-');         // alerts "- foo bar -"
alert('-' + s.trim() + '-');  // alerts "-foo bar-"
</code></pre>
 * @return {String} 已裁剪的字符串。The trimmed string
 */
String.trim = (function(){
	var regexp = /^[\s\t\r\n]+|[\s\t\r\n]+$/g;
	return function(str){
		return str.replace(regexp, '');
	};
})();

/**
 * 日期格式化。
 * 详见我博客文章：http://blog.csdn.net/zhangxin09/archive/2011/01/01/6111294.aspx
 * e.g: new Date().format("yyyy-MM-dd hh:mm:ss")
 * @param 	{String} format
 * @return	{String}
 */
Date.prototype.format = function(format){
    var $1, o = {
         "M+": this.getMonth() + 1 		// 月份，从0开始算
        ,"d+": this.getDate()    		// 日期
        ,"h+": this.getHours()   		// 小时
        ,"m+": this.getMinutes() 		// 分钟
        ,"s+": this.getSeconds() 		// 秒钟
       									// 季度quarter
        ,"q+": Math.floor((this.getMonth() + 3) / 3)  
        ,"S" : this.getMilliseconds() 	// 千秒
    };
    var key, value;

    if (/(y+)/.test(format)){
    	 $1		= RegExp.$1
    	,format = format.replace($1, String(this.getFullYear()).substr(4 - $1));
    }
    // 如果没有指定该参数，则子字符串将延续到 stringvar 的最后。
    for (key in o){
	    if(new RegExp("(" + key + ")").test(format)){
    		 $1		= RegExp.$1
	    	,value	= String(o[key])
	    	,value	= $1.length == 1 ? value : ("00" + value).substr(value.length)
    		,format = format.replace($1, value);
	    }
    } 
    return format;	
}

/**
 * forece Args！ so there's no args, it server as Args!
 * 预设函数的作用域。考查下面两个方法：
	Function.prototype.delegateScope = function (scope){
		var self = this;
	    return function(){
	        return self.apply(scope, arguments);
	    }
	}
	
	Function.prototype.delegateArgs = function(){
	    var 
		     self 	 = this
		    ,args	 = arguments;
		    
	    return function(){
	        return self.apply(this, args);
	    };
	}
 * @return {Function}
 */
Function.prototype.delegate = function(){
    var 
     self 	 = this
    ,scope	 = this.scope 
    ,args	 = arguments
    ,aLength = arguments.length;

    return function(){
        var 
        	 bLength = arguments.length
        	,Length  = (aLength > bLength) ? aLength : bLength;
        
        for(var i = 0; i < Length; i++){
	    	if(arguments[i]){
	    		args[i] = arguments[i];
	    	}
        }   
        // 在 MS jscript下面，rguments作为数字来使用还是有问题，就是length不能自动更新。修正如下:
        args.length = Length;
        return self.apply(scope || this, args);
    };
}

/**
 * 修改function的参数。
 * @param  {Number}		argIndex	指定哪一个参数？
 * @param  {Function}	filterFn 	修改的函数
 * @param  {Boolean}	isArray		还要指定第几个的参数？
 * @return {Function}
 */
Function.prototype.delegateArg = function(argIndex, filterFn, isArray){
    var 
	 self	 = this
	,toArray = Array.prototype.slice;
    	
    return function(){
        var args = toArray.call(arguments, 0);	// 变为真正数组。
        
        if(isArray){
        	args[argIndex] = filterFn.apply(this, arguments);
        }else{
	        args[argIndex] = filterFn.call (this, arguments[argIndex]);
        }
        
        return self.apply(this, args);
    };
}

/**
 * 设置一个后置函数。
 * @param  {Function} composeFn
 * @return {Function}
 */
Function.prototype.after = function(composeFn){
    var self = this;

    return function(){
        var result	= self.apply(this, arguments);
        
        return result && (typeof result.pop != 'undefined') && (typeof result.pop != 'unknown')
    		 ? composeFn.apply(this, result)
    		 : composeFn.call (this, result);
    };
}

/**
 * 设置一个前置函数。
 * @param  {Function} sequenceFn
 * @return {Function}
 */
Function.prototype.before = function(sequenceFn){
    var self = this;

    return function(){
        var result = sequenceFn.apply(this, arguments);

        return result && (typeof result.pop != 'undefined') && (typeof result.pop != 'unknown')
    		 ? self.apply(this, result)
    		 : self.call (this, result);
    };
}

/**
 * @param  {Array} 		arr
 * @return {Function}
 */
Function.prototype.map = function(arr){
	var 
	 self		= this
	,fixArgs	= Array.prototype.slice;
		
	return function(){
		var 
		 args = fixArgs.call(arguments, 1)
	 	,result;
		 	
		for(var i = 0, j = arr.length, result = new Array(j); i < j; i++){
			result[i] = self.apply(this, args.concat(arr[i]));
		}
		return result;
	}
}

/**
 * fn与参数的关系。
 * @param {Array}	arr
 * @param {Object} 	init
 * @param {Any}
 */
Function.prototype.reduce = function(arr, init){
	for(var i = 0, j = arr.length; i < j; i++){
		init = this.call(init || this, arr[i]);
	}
	return init;
}

/**
 * @class $$.Event
 * Event类，就是一个提供事件服务的类，写得简简单单，不求多元、繁复（明显没有比Ext都考虑得多，那是一种方向）。
 * 好像但凡研究JS到一定阶段的人，都要搞清楚事件吧，嗯～必修课。
 * 事件的立论基础大家可以从观察者模式（observable）得到许多灵感，当然就是必须有第三方的“中立”观察者，
 * 一边提供订阅事件的接口，一边让组件触发事件的fireEvent()。
 * 前言：不得不承认，有时候从新写一个库是一件很辛苦的事情。但是相比较之下，直接使用别人写好的软件来修改，难道这样痛苦的程度就会减少吗？
基于事件模式的设计JS的一大好处就是对 Function 天然的良性支持。这一点纵观眼下多少语言都未必能够赋予这种地位，有的即使赋予了，但率直来讲，也没有JS的那样的流通性的地位。试问一下，现今有多少主流语言是对Function这一类型主打的？若没有JS带来的影响，可能这一观念认识知道的人，不计发烧玩家、专家级人马，少之又少，或是又是其他具有“忽悠”价值的概念来代替这一朴质的概念……
事件本身与 Function 形影不离。好多 GUI 系统都以 Function 为编程的中心，因为制定 GUI 的过程往往就是制定 Function 的过程。事件类型一旦明确用哪一个好之后，开发人员所关心的便是这个事件到底应该执行些什么过程。若过程可以以一个变量或对象来表示，再给他起个名字，我们从某个角度上理解，也可以说该过程被抽象化了。抽象有什么好处？就是把一些必要的过程先写好，以后在复用。直截了当的说，我定义函数为一变量，可以全局变量，也可以私有变量，看您怎么用它——还可以当作参数传来传去。
也是一下子这样说有点跨越性太大，不易接受。如果是真的，操作起来究竟有什么好处呢？首先我们得从了解函数其意义的本质上去入手理解。什么？函数是什么？？这不是初中层次的内容？……哎呀，插一句，不是怕大家见笑哦，我就是看了什么好东西，一味东拿来一点西拿来一点拼装一起，还自诩高级，其实没有扎实的基础知识联系，准吃亏！哎～回头来还比不过老老实实的说明一下基础内容。——各位已深明大义的看官请掠过。
 */
$$.event = function(){
	var events = {};
	
	this.addEvents = function(){
		for(var i = 0, j = arguments.length; i < j; i++){
			events[arguments[i].toLowerCase()] = [];
		}
	}
	
	/**
	  * 添加一个事件侦听器。
	  * @param	{String}   name
	  * @param	{Function} fn
	  * @return {this}
	  */
	this.addListener = this.on = function(name, eventHandler) {
		var eventQueen = events[name.toLowerCase()];
		
		if(!eventQueen){
			throw '没有该事件！请使用addEvent()增加事件';
		}

		eventQueen.push(eventHandler);
		
		return this;
	}
	
	/**
	  * 触发事件。
	  * @param {String} name
	  * @param {Array}  args
	  * @return {Boolean}
	  */
	this.fireEvent = function(name) {
		var eventQueen = events[name.toLowerCase()]; // listeners
		var args = eventQueen.length && Array.prototype.slice.call(arguments, 1); 
					
		var result;
		var output = [];
		
		for (var i = 0, j = eventQueen.length; i < j; i++) {
			result = eventQueen[i].apply(this, args);
			
			if(result === false){
				break;
			}else{
				output.push(result);
			}
		}
	
		return output;
	}
	
	/**
	  * 移除事件侦听器。须传入原来的函数句柄。
	  * @param {String}   name
	  * @param {Function} fn
	  * @return {this}
	  */
	this.removeListener = function(name, fn) {
		if (events[name]) {
			Array.remove(events[name], fn);
		}
		return this;
	}
}
 
/**
 *  通用的跟踪方法，用于调试所用。Debug
 */
$$.trace = function(a){
	if(typeof console != 'undefined' && console.log){
		console.log(a);
	}else if(typeof $$.console != 'undefined' && $$.console.log){
		$$.console.log(a);
	}else if(typeof(Response) != 'undefined' ){
        Response.write(a);
    }else{
		var fn = arguments.callee;
		fn.count++;		// using alert()
		if(fn.count > fn.maxShow ){
			return;
		}else if(fn.count == fn.maxShow ){
	        alert('Too much errors! It remains many errors, but not going to show.');
	    }else{
	        alert(a);
	    }
    }
}

$$.trace.count		= 0;
$$.trace.maxShow	= 5;

l = $$.trace;// very short short hand so that you can call l(xxx) easily.

$$.JSON = (function() {
	function NumberAs(num) {
		return num.toString();
	}
	
//	function StringAs(string) {
//		return '"' + object.replace(/(\\|\")/g, "\\$1").replace(/\n|\r|\t/g,
//				function() {
//					var a = arguments[0];
//					return (a == '\n') ? '\\n' : (a == '\r')
//							? '\\r'
//							: (a == '\t') ? '\\t' : ""
//				}) + '"';
//	}
	
	function StringAs(string) {
		var reg = StringAs.reg;
		return '"' + string.replace(reg, "\\$1") + '"';
	}
	StringAs.reg = /(\\|\"|\n|\r|\t)/g

	function ObjectAs(object) {
		var 
		 value
		,results = []
		,callee  = $$.JSON.encode;

		for (var i in object) {
			value = callee(object[i]);
			if (value !== undefined) {
				results.push(callee(i) + ':' + value);
			}
		}
		return '{' + results.join(',') + '}';
	}

	function ArrayAs(array) {
		var 
		 value
		,results = []
		,callee  = $$.JSON.encode;

		for (var i = 0, j = array.length; i < j; i++) {
			value = callee(array[i]);
			if (value !== undefined) {
				results.push(value);
			};
		}
		return '[' + results.join(',') + ']';
	}

	/**
	 * "new Date(" + obj.valueOf() + ")";
	 */
	function DateAs(date){
		return "new Date(" + date.valueOf() + ")";// @todo
		
		var makeTwoDigits = DateAs.makeTwoDigits;
		
		return date.getUTCFullYear() 				+ '-'
			+ makeTwoDigits(date.getUTCMonth() + 1) + '-'
			+ makeTwoDigits(date.getUTCDate())		+ 'T'
			+ makeTwoDigits(date.getUTCHours())		+ ':'
			+ makeTwoDigits(date.getUTCMinutes()) 	+ ':'
			+ makeTwoDigits(date.getUTCSeconds())	+ 'Z';
	}
	
	/**
	 * Format integers to have at least two digits.
	 */
	DateAs.makeTwoDigits = function(n) {
		return n < 10 ? '0' + n : n;
	}
	
	/**
	 * decode日期函数，日期格式是ISO，以下就是这个转换函数，蛮有意思的是with(){} statement的用法:)
	 */
	function decodeDate(str){
		var getDate = decodeDate.reg;
		if(!getDate.test(str)){
			return null; 
		}else{
			with(getDate.exec(str)){
				return new Date([1], [2], [3], [4], [5], [6]);
			} 
  		} 
	}
	
	decodeDate.reg = /(\d{4})-(\d{1,2})-(\d{1,2})T(\d{1,2}):(\d{1,2}):(\d{1,2})/;

	return {
		decode : function() {
			return eval("(" + arguments[0] + ')');
		},
		
		// uneval
		encode : function(object) {
			if (object === null) {
				return 'null';
			}else if(typeof object == 'undefined'){
				return;
			}
			
			if(object instanceof Boolean){
				return NumberAs(object);
			}else if(object instanceof Number){
				return NumberAs(object);
			}else if(object instanceof String){
				return StringAs(object);
			}else if(object instanceof Array){
				return ArrayAs(object);
			}else if(object instanceof Date){
				return DateAs(object);
			}else if(object instanceof RegExp){
				return;
			}
			
			switch (object.constructor) {
				case Boolean :
					return NumberAs(object);
					break;
				case Number :
					return NumberAs(object);
					break;
				case String :
					return StringAs(object);
					break;
				case Array :
					return ArrayAs(object);
					break;
				case Date :
					return DateAs(object);
					break;
				case RegExp :
					return;
					break;
			}
			
			switch (typeof object){
				case 'undefined' :
				case 'unknown' :
				case 'function' :
				case 'regexp' :
					return;
					break;
				case 'boolean' :
				case 'number' :
					return NumberAs(object);
					break;
				case 'date' :
					return DateAs(object);
					break;
				case 'string' :
					return StringAs(object);
				case 'object' :
					return ObjectAs(object);
					break;
				case 'array' :
					return ArrayAs(object);
			}
		}
	};
})();